package com.gitee.wsl.data.source

import arrow.atomic.Atomic
import arrow.atomic.update
import arrow.core.nonFatalOrThrow
import arrow.core.prependTo
import com.gitee.wsl.ioDispatcher
import com.gitee.wsl.resource.R
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.withContext
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
import kotlin.coroutines.cancellation.CancellationException

@ResourceDSL
 interface ResourceScope : AutoCloseScope {

    /**
     * Compose another [Resource] program into this [ResourceScope].
     * All [release] functions [install]ed into the [Resource] lambda will be installed in this [ResourceScope] while respecting the FIFO order.
     */
    @ResourceDSL
    suspend fun <A> Resource<A>.bind(): A = this()

    /**
     * Install [A] into the [ResourceScope].
     * Its [release] function will be called with the appropriate [ExitCase] if this [ResourceScope] finishes.
     * It results either in [ExitCase.Completed], [ExitCase.Cancelled] or [ExitCase.Failure] depending on the terminal state of [Resource] lambda.
     */
    @ResourceDSL
    suspend fun <A> install(
        acquire: suspend AcquireStep.() -> A,
        release: suspend (A, ExitCase) -> Unit,
    ): A = withContext(NonCancellable) {
        acquire(AcquireStep).also { a -> onRelease { release(a, it) } }
    }

    /** Composes a [release] action to a [Resource] value before binding. */
    @ResourceDSL
    suspend infix fun <A> Resource<A>.release(release: suspend (A) -> Unit): A =
        bind().also { a -> onRelease { release(a) } }

    /** Composes a [releaseCase] action to a [Resource] value before binding. */
    @ResourceDSL
    suspend infix fun <A> Resource<A>.releaseCase(release: suspend (A, ExitCase) -> Unit): A =
        bind().also { a -> onRelease { release(a, it) } }

    override fun onClose(release: (Throwable?) -> Unit): Unit = onRelease { release(it.errorOrNull) }

    infix fun onRelease(release: suspend (ExitCase) -> Unit)
}


internal class ResourceScopeImpl : ResourceScope {

    private val finalizers: Atomic<List<suspend (ExitCase) -> Unit>> = Atomic(emptyList())

    override fun onRelease(release: suspend (ExitCase) -> Unit) {
        finalizers.update(release::prependTo)
    }

    suspend fun cancelAll(exitCase: ExitCase) {
        withContext(NonCancellable) {
            finalizers.getAndSet(emptyList()).fold(exitCase.errorOrNull) { acc, finalizer ->
                acc.add(runCatching { finalizer(exitCase) }.exceptionOrNull())
            }
        }?.let { throw it }
    }

    private fun Throwable?.add(other: Throwable?): Throwable? {
        if (other !is CancellationException) other?.nonFatalOrThrow()
        return this?.apply {
            other?.let { addSuppressed(it) }
        } ?: other
    }
}


/**
 * Creates a [Resource] from an [AutoCloseable], which uses [AutoCloseable.close] for releasing.
 *
 * ```kotlin
 * import arrow.fx.coroutines.resourceScope
 * import arrow.fx.coroutines.autoCloseable
 * import java.io.FileInputStream
 *
 * suspend fun copyFile(src: String, dest: String): Unit =
 *   resourceScope {
 *     val a: FileInputStream = autoCloseable { FileInputStream(src) }
 *     val b: FileInputStream = autoCloseable { FileInputStream(dest) }
 *     /** read from [a] and write to [b]. **/
 *   } // Both resources will be closed accordingly to their #close methods
 * ```
 * <!--- KNIT example-resource-10.kt -->
 */
@OptIn(ExperimentalContracts::class)
@ResourceDSL
suspend fun <A : AutoCloseable> ResourceScope.autoCloseable(
    closingDispatcher: CoroutineDispatcher = Dispatchers.ioDispatcher,
    autoCloseable: suspend () -> A,
): A {
    contract {
        callsInPlace(autoCloseable, InvocationKind.EXACTLY_ONCE)
    }
    // This is install({ autoCloseable() } ) { s: A, _ -> withContext(closingDispatcher) { s.close() } }
    // but inlined because `install` can't have a contract (since it's a member)
    return withContext(NonCancellable) {
        val s = autoCloseable()
        onRelease { withContext(closingDispatcher) { s.close() } }
        s
    }
}


fun <A : AutoCloseable> R.autoCloseable(
    closingDispatcher: CoroutineDispatcher = Dispatchers.ioDispatcher,
    autoCloseable: suspend () -> A,
): Resource<A> = resource {
    autoCloseable(closingDispatcher, autoCloseable)
}